package client.zookeeper.impl;

import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import org.apache.log4j.Logger;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.cli.CliException;
import org.apache.zookeeper.data.ACL;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONValidator;

import client.zookeeper.IZookeeperClient;
import client.zookeeper.ZookeeperClientController;
import client.zookeeper.ZookeeperTreeNode;
import client.zookeeper.impl.DO.ZookeeperPathTreeNode;
import client.zookeeper.impl.DO.ZookeeperRootTreeNode;
import core.AlertUtil;
import core.FormDialog;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;

/**
 * 单台Zookeeper连接客户端
 * @author self
 *
 */
public class ZookeeperClientImpl implements IZookeeperClient,Watcher {

	//客户端名称
	private static final String CLIENT_NAME = "Zookeeper客户端";
	
	//应用日志对象
	private Logger appLogger;
	
	//界面控制器组件对象
	private ZookeeperClientController controller;
	
	//Jedis客户端
	private ZooKeeper zk;
	
	private ZookeeperCmd zkCmd;
	
	private ConnectInfo connectInfo;
	
	@Override
	public String getName() {
		return CLIENT_NAME;
	}
	
	@Override
	public void init(ZookeeperClientController controller) {
		this.controller = controller;
		this.appLogger =controller.getAppLogger();
		
		appLogger.info("初始化客户端组件！");
		createConnection();
	}
	
	/**
	 * 创建连接
	 * @return
	 */
	private boolean createConnection() {
		appLogger.info("创建Socket连接！");
		TextArea serverTextArea = new TextArea("127.0.0.1:2181");
		FormDialog newConnForm = new FormDialog("新建链接"
				,new FormDialog.Item("主机：", serverTextArea));
		Optional<Boolean> result = newConnForm.showAndWait();
		if(result.isPresent()) {
			String services = serverTextArea.getText();
			appLogger.info("Socket连接参数 -> host:"+services);
			this.connectInfo = new ConnectInfo(services);
			try {
				zk = new ZooKeeper(services, 5000, this);
				zkCmd = new ZookeeperCmd(zk,new CmdPrintStream(System.out, controller.getCmdTextArea()));
				this.controller.getCmdTextArea().setText("已连接！\n");
				appLogger.info("已连接到Zookeeper服务器。");
				initTreeView();
			} catch (IOException e) {
				AlertUtil.exception("连接异常", e);
			}
		}
		return false;
	}

	/**
	 * 初始化TreeView树
	 */
	private void initTreeView() {
		appLogger.info("初始化Zookeeper树结构。");
		TreeView<ZookeeperTreeNode> zkTreeView = controller.getZkTreeView();
		appLogger.info("初始化树节点！");
		TreeItem<ZookeeperTreeNode> root = this.getRootTreeItem();
		zkTreeView.setRoot(root);
		
		appLogger.info("添加节点选择监听事件！");
		//节点选择事件处理
		zkTreeView.getSelectionModel().selectedItemProperty().addListener((observable,oldValue,newValue)->{
			appLogger.info("Zookeeper树节点被点击 -> oldValue:"+oldValue+",newValue:"+newValue);
			TreeItem<ZookeeperTreeNode> currentSelectedItem = (TreeItem<ZookeeperTreeNode>) newValue;
			if(currentSelectedItem==null) return;
			
			//设置节点信息
			ZookeeperTreeNode selectedNode = currentSelectedItem.getValue();
			String itemInfo = this.getItemInfo(selectedNode);
			controller.getInfoTextArea().setText(itemInfo);
			
			//如果是DB节点则刷新子节点
			if(!selectedNode.isLeaf() && !selectedNode.isLoaded()) {
				currentSelectedItem.getChildren().clear();
				List<TreeItem<ZookeeperTreeNode>> childNodeList = this.getChildren(selectedNode);
				currentSelectedItem.getChildren().addAll(childNodeList);
				currentSelectedItem.setExpanded(true);
			}
			
			selectedNode.setLoaded(true);
		});
		
		appLogger.info("设置树节点右键菜单！");
		//设置TreeView树邮件菜单
		MenuItem refreshItem = new MenuItem("刷新"); 
		refreshItem.setOnAction((event)->{
			appLogger.info("执行[刷新]动作！");
			TreeItem<ZookeeperTreeNode> selectedItem = zkTreeView.getSelectionModel().getSelectedItem();
			ZookeeperTreeNode zkTreeNode = selectedItem.getValue();
			//刷新信息
			controller.getInfoTextArea().setText(this.getItemInfo(zkTreeNode));
			
			//刷新子节点
			if(!zkTreeNode.isLeaf()) {
				selectedItem.getChildren().clear();
				selectedItem.getChildren().addAll(this.getChildren(zkTreeNode));
				selectedItem.setExpanded(true);
			}
		});
		
		MenuItem disconnectItem = new MenuItem("断开连接");
		disconnectItem.setOnAction((event)->{
			appLogger.info("执行[断开连接]动作！");
			try {
				this.close();
				controller.getZkTreeView().setRoot(null);
				controller.getZkTreeView().setContextMenu(null);
				controller.getInfoTextArea().clear();
				controller.getCmdTextArea().setText("连接已断开！\n");
			} catch (IOException e) {
				AlertUtil.exception(e);
			}
			
		});
		
		MenuItem createPathItem = new MenuItem("创建");
		createPathItem.setOnAction((event)->{
			appLogger.info("执行[创建]动作！");
			TreeItem<ZookeeperTreeNode> selectedItem = zkTreeView.getSelectionModel().getSelectedItem();
			ZookeeperTreeNode zkTreeNode = selectedItem.getValue();
			if(zkTreeNode instanceof ZookeeperPathTreeNode) {
				String currPath = ((ZookeeperPathTreeNode) zkTreeNode).getPath();
				Map<String,CreateMode> createModeMap=new HashMap<String,CreateMode>();
				createModeMap.put("持久化节点", CreateMode.PERSISTENT);
				createModeMap.put("持久化有序节点", CreateMode.PERSISTENT_SEQUENTIAL);
				createModeMap.put("临时节点", CreateMode.EPHEMERAL);
				createModeMap.put("临时有序节点", CreateMode.EPHEMERAL_SEQUENTIAL);
				createModeMap.put("容器节点", CreateMode.CONTAINER);
				createModeMap.put("持久化节点(带有效期)", CreateMode.PERSISTENT_WITH_TTL);
				createModeMap.put("持久化有序节点(带有效期)", CreateMode.PERSISTENT_SEQUENTIAL_WITH_TTL);
				ComboBox<String> typeComboBox = new ComboBox<String>(
						FXCollections.observableArrayList(createModeMap.keySet()));
				typeComboBox.getSelectionModel().select("持久化节点"); //默认选项
				TextField nameTextFiled =new TextField();
				FormDialog createNodeDialog = new FormDialog("新建节点",
						new FormDialog.Item("类型：",typeComboBox),
						new FormDialog.Item("名称",nameTextFiled));
				Optional<Boolean> result = createNodeDialog.showAndWait();
				if(result.isPresent()) {
					String type = typeComboBox.getValue();
					String name = nameTextFiled.getText();
					String path = currPath.endsWith("/")?currPath+name:currPath+"/"+name;
					try {
						zk.create(path, null, Ids.OPEN_ACL_UNSAFE, createModeMap.get(type));
						
						//刷新子节点
						selectedItem.getChildren().clear();
						List<TreeItem<ZookeeperTreeNode>> childNodeList = this.getChildren(zkTreeNode);
						selectedItem.getChildren().addAll(childNodeList);
						selectedItem.setExpanded(true);
					} catch (KeeperException | InterruptedException e) {
						AlertUtil.exception("创建失败", e);
					}
				}
			}
		});
		
		MenuItem delPathItem = new MenuItem("删除");
		delPathItem.setOnAction((event)->{
			appLogger.info("执行[删除]动作！");
			TreeItem<ZookeeperTreeNode> selectedItem = zkTreeView.getSelectionModel().getSelectedItem();
			ZookeeperTreeNode zkTreeNode = selectedItem.getValue();
			if(zkTreeNode instanceof ZookeeperPathTreeNode) {
				String path = ((ZookeeperPathTreeNode) zkTreeNode).getPath();
				try {
					zk.delete(path, -1);
					selectedItem.getParent().getChildren().clear(); //清理当前层级节点，重新加载
				} catch (InterruptedException | KeeperException e) {
					AlertUtil.exception(e);
				}
			}
		});
		
		zkTreeView.setContextMenu(new ContextMenu(disconnectItem)); 
		zkTreeView.setOnContextMenuRequested((event)->{
			zkTreeView.getContextMenu().getItems().clear();
			TreeItem<ZookeeperTreeNode> selectedItem = zkTreeView.getSelectionModel().getSelectedItem();
			ZookeeperTreeNode zkTreeNode = selectedItem.getValue();
			if(zkTreeNode instanceof ZookeeperRootTreeNode) {
				zkTreeView.getContextMenu().getItems().addAll(refreshItem,disconnectItem);
			}else if(zkTreeNode instanceof ZookeeperPathTreeNode) {
				zkTreeView.getContextMenu().getItems().addAll(refreshItem,createPathItem,delPathItem);
			}
		});
		
		appLogger.info("Zookeeper树结构初始化完成！");
	}
	
	/**
	 * 获取根节点
	 * @return　根节点对象
	 */
	private TreeItem<ZookeeperTreeNode> getRootTreeItem() {
		appLogger.info("获取Zookeeper树根节点！");
		ZookeeperRootTreeNode rootNode = new ZookeeperRootTreeNode(connectInfo.getConnStr());
		TreeItem<ZookeeperTreeNode> rootItem = new TreeItem<ZookeeperTreeNode>(rootNode);
		rootItem.setExpanded(true);
		rootItem.getChildren().addAll(this.getChildren(rootNode));
		return rootItem;
	}
	
	/**
	 * 获取子节点集合
	 * @param selectedNode　当前选中节点
	 * @return　子节点集合
	 */
	private List<TreeItem<ZookeeperTreeNode>> getChildren(ZookeeperTreeNode selectedNode) {
		appLogger.info("获取Zookeeper树子节点！当前节点："+selectedNode);
		if(selectedNode instanceof ZookeeperRootTreeNode) {
			List<TreeItem<ZookeeperTreeNode>> childrenList = new ArrayList<>();
			ZookeeperPathTreeNode pathNode = new ZookeeperPathTreeNode("/","/");
			TreeItem<ZookeeperTreeNode> pathItem = new TreeItem<ZookeeperTreeNode>(pathNode);
			childrenList.add(pathItem);
			return childrenList;
		}else if(selectedNode instanceof ZookeeperPathTreeNode) {
			ZookeeperPathTreeNode currPathNode = (ZookeeperPathTreeNode)selectedNode;
			String currPath = currPathNode.getPath();
			List<TreeItem<ZookeeperTreeNode>> childrenList = new ArrayList<>();
			try {
				List<String> children = zk.getChildren(currPath, false);
				for(String name:children) {
					String path = currPath.endsWith("/")?currPath+name:currPath+"/"+name;
					ZookeeperPathTreeNode pathNode = new ZookeeperPathTreeNode(path,name);
					TreeItem<ZookeeperTreeNode> pathItem = new TreeItem<ZookeeperTreeNode>(pathNode);
					childrenList.add(pathItem);
				}
			} catch (KeeperException | InterruptedException e) {
				AlertUtil.exception("获取子节点异常", e);
			}
			return childrenList;
		}
		return null;
	}

	/**
	 * 获取节点信息
	 * @param selectedNode　当前选中节点
	 * @return　节点信息
	 */
	private String getItemInfo(ZookeeperTreeNode selectedNode) {
		appLogger.info("获取Zookeeper树节点详情信息。当前节点："+selectedNode);
		if(selectedNode instanceof ZookeeperRootTreeNode) {
			return "";
		}else if(selectedNode instanceof ZookeeperPathTreeNode) {
			String path = ((ZookeeperPathTreeNode) selectedNode).getPath();
			try {
				StringBuilder strBuilder = new StringBuilder();
				List<ACL> aclList = zk.getACL(path, null);
				for(ACL acl:aclList) {
					strBuilder.append(acl.toString()).append("\n");
				}
				strBuilder.append("------------------------------------------------------------\n");
				byte[] data=zk.getData(path, false, null);
				if(data == null) {
					return "";
				}
				String nodeData = new String(data,StandardCharsets.UTF_8);
				try {
					if(JSONValidator.from(nodeData).validate()) {
						//对json进行格式化输出
						nodeData=JSON.toJSONString(JSON.parse(nodeData),true);
					}
				} catch (Exception e) {
					//非json字符串
				}
				strBuilder.append(nodeData);
				return strBuilder.toString();
			} catch (KeeperException | InterruptedException e) {
				AlertUtil.exception(e);
			}
		}
		return null;
	}
	
	@Override
	public String exeCmd(String cmd) {
		if(cmd==null||"".equals(cmd.trim())) {
			return null;
		}
		appLogger.info("执行命令："+cmd);
		//命令如果没有换行符则补充一个
		if(!cmd.endsWith("\n")) {
			cmd+="\n";
		}
		
		try {
			zkCmd.executeLine(cmd);
		} catch (CliException | InterruptedException | IOException e) {
			e.printStackTrace();
		}
		
		return null;
	}

	@Override
	public void close() throws IOException {
		if(zk!=null) {
			try {
				zk.close();
			} catch (InterruptedException e) {
				AlertUtil.exception(e);
			}
		}
	}

	@Override
	public void process(WatchedEvent event) {
		appLogger.info("Watch received event: "+event);
	}
	
	/**
	 * 连接信息
	 * @author self
	 *
	 */
	private static class ConnectInfo{
		private String connStr;
		
		public ConnectInfo(String connStr) {
			this.connStr = connStr;
		}
		
		public String getConnStr() {
			return connStr;
		}
	}
	
	/**
	 * 自定义命令输出组件
	 * @author self
	 *
	 */
	private static class CmdPrintStream extends PrintStream{
		
		private TextArea logTextArea;
		
		public CmdPrintStream(OutputStream out,TextArea logTextArea) {
			super(out);
			this.logTextArea = logTextArea;
		}
		
		@Override
		public void write(byte[] buf, int off, int len) {
			final String message = new String(buf,off,len);
			//将消息输入到日志组件内
			Platform.runLater(()->{
				logTextArea.appendText(message);
			});
		}
	}
}
